package com.atguigu.tingshu.album.service.impl;

import cn.hutool.core.util.IdUtil;
import com.atguigu.tingshu.album.service.TestService;
import org.apache.commons.lang3.StringUtils;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;

/**
 * @author: atguigu
 * @create: 2023-10-07 08:54
 */
@Service
public class TestServiceImpl implements TestService {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;


    //@Override
    //public synchronized void testLock() {
    //    try {
    //        //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
    //        String value = stringRedisTemplate.opsForValue().get("num");
    //        if (StringUtils.isBlank(value)) {
    //            return;
    //        }
    //        //2.对获取到值进行+1操作
    //        int num = Integer.parseInt(value);
    //        stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));
    //
    //    } catch (NumberFormatException e) {
    //        e.printStackTrace();
    //    }
    //}

    /*锁名称*/
    private static final String lockKey = "lock";
    /*锁的值*/
    //private static final String lockValue = "lock";

    /**
     * 采用SpringDataRedis实现分布式锁
     */
    /*@Override
    public void testLock() {

        //一、尝试获取分布式锁
        //版本1：采用setnx 问题：锁未设置过期时间，可能导致死锁
        //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue);
        //版本2：为锁设置过期时间 问题：加锁跟设置锁过期时间无法保证原子性 ，依然存在死锁问题
        //stringRedisTemplate.expire(lockKey, 5, TimeUnit.SECONDS);
        //版本3：采用set k v EX NX 一个命令实现加锁及过期时间设置 问题：锁被不同线程互相释放
        //Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
        //版本4：判断锁是否为当前线程的，固锁的值：产生线程唯一标识 UUID
        String lockVal = IdUtil.fastSimpleUUID();
        Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(lockKey, lockVal, 5, TimeUnit.SECONDS);

        //二、加锁成功后才能执行业务逻辑
        if (flag) {
            try {
                //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
                String value = stringRedisTemplate.opsForValue().get("num");
                if (StringUtils.isBlank(value)) {
                    return;
                }
                //2.对获取到值进行+1操作
                int num = Integer.parseInt(value);
                stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

            } catch (NumberFormatException e) {
                e.printStackTrace();
            } finally {
                //业务执行将锁释放
                //版本4：判断锁是否为当前线程的，如果是再删除
                //if (lockVal.equals(stringRedisTemplate.opsForValue().get(lockKey))) {
                //    stringRedisTemplate.delete(lockKey);
                //}
                //lua脚本 保证多个Redis命令原子性
                String text = "if redis.call(\"get\",KEYS[1]) == ARGV[1]\n" +
                        "then\n" +
                        "    return redis.call(\"del\",KEYS[1])\n" +
                        "else\n" +
                        "    return 0\n" +
                        "end";
                DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
                redisScript.setScriptText(text);
                redisScript.setResultType(Long.class);
                stringRedisTemplate.execute(redisScript, Arrays.asList(lockKey), lockVal);
            }
        } else {
            try {
                //三、加锁失败，自旋（再继续尝试获取分布式锁）
                Thread.sleep(200);
                this.testLock();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }*/


    @Autowired
    private RedissonClient redissonClient;


    /*
    使用Redisson框架实现分布式锁
    * */
    @Override
    public void testLock() {
        //一、获取分布式锁
        //① 创建锁对象
        RLock lock = redissonClient.getLock("lock");

        //② 获取锁
        lock.lock();  //当前线程阻塞到一直获取到锁为止
        //lock.lock(5, TimeUnit.SECONDS); //加锁成功后，锁过期时间指定为5s
        //boolean flag = lock.tryLock();  //加锁成功则返回true，锁被占用当前线程加锁失败返回false
        //lock.tryLock(5, TimeUnit.SECONDS); //锁被占用，在指定时间内获取锁成功返回true，等待超时返回false
        //lock.tryLock(3, 5, TimeUnit.SECONDS); ////锁被占用，在指定时间内获取锁成功返回true,锁过期时间为5s，等待超时返回false

        //二、加锁成功后才能执行业务逻辑
        try {
            //1.从Redis缓存中获取key="num"的值  保证redis中存在"num"(手动提前在redis中创建key)
            String value = stringRedisTemplate.opsForValue().get("num");
            if (StringUtils.isBlank(value)) {
                return;
            }
            //2.对获取到值进行+1操作
            int num = Integer.parseInt(value);
            stringRedisTemplate.opsForValue().set("num", String.valueOf(++num));

        } catch (NumberFormatException e) {
            e.printStackTrace();
        } finally {
            //③业务执行完毕释放锁
            lock.unlock();
        }
    }

    //@Autowired
    //private RedissonClient redissonClient;


    @Override
    public void testRead() {
        //1.先创建读锁对象
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock rLock = rwLock.readLock();
        //2.获取读锁
        rLock.lock(5, TimeUnit.SECONDS);
        //3.执行查询数据方法，将查询结果放入缓存
        System.out.println("执行查询数据方法，将查询结果放入缓存");
        //4.将读锁释放 故意不释放读锁，5秒后读锁自动释放
    }

    @Override
    public void testWrite() {
        //1.先创建写锁对象
        RReadWriteLock rwLock = redissonClient.getReadWriteLock("rwLock");
        RLock wLock = rwLock.writeLock();
        //2.获取写锁
        wLock.lock(5, TimeUnit.SECONDS);
        //3.执行修改数据库逻辑
        System.out.println("执行修改数据方法，删除删除");
        //4.将写锁释放 故意不释放写锁，5秒后写锁自动释放
    }
}
